import { Picker3D, PickerMgr } from '@amcax/renderer';
import {
  getLastDistPoint,
  isEffectiveValue,
  pointProjectOnLine,
} from '@app-cad/common/utils';
import { ctx2 } from '@app-cad/module';
import { Vector3, Vector3Tuple } from 'three-legacy';
import { CSPoint, CSys, KEYBOARD_KEY, MathTool } from '@amcax/base';
import { Arrow } from './arrow';

type Type = {
  point: Vector3;
  is_move?: boolean;
  group?: 'toolsGroup' | 'assistPoints';
  key?: 'assistPoints' | 'assistPointFixs';
};

type MousePoint = {
  x: number;
  y: number;
};

export enum PlaneEnum {
  XOY = 'XY',
  XOZ = 'XZ',
  YOZ = 'YZ',
  FREE = 'FREE',
}
export type PlaneType =
  | PlaneEnum.XOY
  | PlaneEnum.YOZ
  | PlaneEnum.XOZ
  | PlaneEnum.FREE;

export enum MdEn {
  FACE = 'face',
  PLANE = 'plane',
  LINE = 'line',
  POINT = 'point',
  NONE = 'none',
  LINELENGTH = 'lineLength',
}
type ModeType = 'face' | 'line' | 'point' | 'none' | 'lineLength' | 'plane';
// 点在实体上的 实体的类型
type EntityType = MdEn.FACE | MdEn.LINE | MdEn.POINT | MdEn.NONE;
export default abstract class Base {
  setting = null;
  picker = PickerMgr.getPicker<Picker3D>('3d');
  limitPick; // 限制拾取
  assistPoints: CSPoint[] = [];
  snapAssistPoint: CSPoint;
  pickInfo;
  arrow: Arrow;
  edgeData;
  centerPoint: CSPoint;
  mode: ModeType[] = [MdEn.NONE];
  point: Vector3;
  entityId: string;
  containIds: string[] = null;
  entityType: EntityType;
  isPick: boolean = false;
  planeNormal: Vector3;
  planeOrigin: Vector3;
  faceCSys: CSys;
  abstract destroy(): void;
  abstract unregisterEvents(): void;
  abstract registerEvents(): void;
  setLimitPick(limitPick) {
    this.limitPick = limitPick;
  }
  setContainIds = (ids) => {
    this.containIds = ids;
  };
  exitFunc: () => void;
  setMode = (mode: ModeType[]) => {
    this.mode = mode;
  };
  reset() {
    this.removePointArrow();
    this.point = null;
    this.entityId = null;
    this.centerPoint = null;
    this.edgeData = null;
    this.snapAssistPoint = null;
    this.pickInfo = null;
    this.containIds = null;
    this.mode = [MdEn.NONE];
    this.isPick = false;
    this.limitPick = null;
  }
  mouseUp = (e) => {
    if (e.button.onLeft) {
      let len = null;
      if (this.mode.includes(MdEn.LINELENGTH)) {
        this.picker.select(e);
        len = this.getLength();
      }
      return {
        point: this.point?.toArray() || [0, 0, 0],
        entityId: this.entityId,
        len: len,
      };
    }
    return null;
  };
  getLength = () => {
    let le = 0;
    let point = null;
    this.edgeData?.forEach((item) => {
      if (point) {
        le += point.distanceTo(item);
      }
      point = item;
    });
    return le;
  };
  mouseMove = (e, hasArrow = true) => {
    const mode = this.mode;
    if (mode.includes(MdEn.NONE)) {
      return;
    }
    const pick = this.picker.pick(e, this.getOptions(), this.setting);
    this.pickInfo = pick;
    this.entityId = null;
    this.point = null;
    if (!pick?.assisObj) {
      this.removeAssistPoint();
      this.entityType = MdEn.NONE;
      return;
    }
    if (mode.includes(MdEn.LINELENGTH)) {
      this.edgeData = pick?.edgeData;
      this.entityId = pick?.edgeId;
      return;
    }
    pick.assisObj.visible = false;
    if (pick?.vertexId && this.mode?.includes(MdEn.POINT)) {
      const point = pick.assisObj.userData.vertex;
      this.point = point;
      this.createAssistPoint({ point: point });
      this.removeArrow();
      this.entityId = pick?.vertexId;
      this.entityType = MdEn.POINT;
    } else if (this.isExecute(pick?.edgeId, MdEn.LINE)) {
      this.snapPointOnLine(e, pick.edgeData, null, hasArrow);
      this.entityId = pick?.edgeId;
      this.entityType = MdEn.LINE;
    } else if (
      this.isExecute(pick?.meshId, MdEn.FACE) ||
      (pick.assisObj?.csys && this.isExecute(pick?.meshId, MdEn.PLANE))
    ) {
      const rayObj = this.raycaster(e);
      this.createAssistPoint({ point: rayObj?.point });
      this.point = rayObj?.point;
      this.removeArrow();
      this.entityId = pick?.meshId;
      this.entityType = MdEn.FACE;
    }
  };
  isExecute = (id: string, state: ModeType) => {
    if (!id || !this.mode?.includes(state)) {
      return false;
    }
    // 包含的ids如果包含的ids存在，判断id是否在包含的ids中
    if (!this.containIds || this.containIds.includes(id)) {
      this.pickInfo.assisObj.visible = true;
      return true;
    } else {
      return false;
    }
  };
  getOptions() {
    const options = {};
    this.mode.forEach((key) => {
      if (key === MdEn.LINELENGTH) {
        options[MdEn.LINE] = true;
      } else if (key === MdEn.PLANE) {
        options[MdEn.FACE] = true;
        options['planeOnly'] = true;
      } else {
        options[key] = true;
      }
    });
    return options;
  }
  snapPointOnLine = (e, edgeData, center?, hasArrow: boolean = true) => {
    const rayObj = this.raycaster(e);
    const v3 = rayObj?.point || rayObj?.['pointOnLine'];
    if (!v3) {
      return;
    }
    const p = getLastDistPoint(v3, edgeData);
    const point = pointProjectOnLine<Vector3>(
      v3,
      p[0],
      new Vector3().subVectors(p[1], p[0]).normalize(),
    );
    if (hasArrow) {
      this.setArrow({
        vec: new Vector3().subVectors(p[1], p[0]),
        arrowEnd: point,
        center,
      });
    }
    this.point = point;
    this.createAssistPoint({ point: point });
  };
  removePointArrow = () => {
    this.removeArrow();
    this.pickInfo = null;
    this.assistPoints = [];
    ctx2.cadScene.toolsGroup.clear();
  };
  removeArrow = () => {
    this.arrow?.destroy();
    this.arrow = null;
    ctx2.viewer.requestRender();
  };
  // 添加辅助点
  protected createAssistPoint({
    point,
    is_move = true,
    key = 'assistPoints',
    group = 'toolsGroup',
  }: Type) {
    if (!isEffectiveValue(point)) {
      return null;
    }
    const assistPoint = this.createPoint(point);
    if (!is_move || this[key]?.length == 1) {
      this[key].push(assistPoint);
      ctx2.cadScene[group].add(assistPoint);
    } else {
      ctx2.cadScene[group]?.remove(this[key][this[key].length - 1]);
      ctx2.cadScene[group]?.add(assistPoint);
      this[key][this[key].length - 1] = assistPoint;
      ctx2.viewer.requestRender();
    }
    this.snapAssistPoint = assistPoint;
    return assistPoint;
  }
  createPoint = (point, pointType = 'midPoint') => {
    if (!point) {
      return null;
    }
    const assistPoint: CSPoint = new CSPoint({
      vertex: point,
      pointType: pointType,
      size: 10,
    });
    assistPoint.hoverOn();
    return assistPoint;
  };
  setArrow = (params) => {
    if (!this.arrow) {
      this.arrow = new Arrow(params);
      this.arrow.addTo(ctx2.cadScene.meshGroup);
    } else {
      this.arrow.update(params);
    }
  };
  raycaster(e) {
    const raycaster = ctx2.viewer.sceneSetup.createRaycaster(
      e.mouse.x,
      e.mouse.y,
      0,
      20,
    );
    const rays = raycaster.intersectObjects(
      ctx2.cadScene.assisGroup.children,
      true,
    );
    return rays?.[0];
  }
  getCenter(edgeData) {
    let center = null;
    if (edgeData?.length % 2 === 0) {
      center = new Vector3()
        .addVectors(
          edgeData[edgeData.length / 2 - 1],
          edgeData[edgeData.length / 2],
        )
        .divideScalar(2);
    } else if (edgeData?.length > 0) {
      center = edgeData[Math.floor(edgeData.length / 2)];
    }
    return center;
  }
  protected removeAssistPoint() {
    ctx2.cadScene.toolsGroup.remove(this.snapAssistPoint);
  }
  onKeyUp = (e) => {
    if (e.key === KEYBOARD_KEY.ESC) {
      this.destroy();
      this.exitFunc?.();
    } else if (e.key === KEYBOARD_KEY.ENTER) {
      this.onEnter();
    }
  };
  onEnter = () => {};
  addExit(exitFunc: () => void) {
    this.exitFunc = exitFunc;
  }
  getMouseWorldVector(mousePoint: MousePoint): Vector3 {
    const normal = this.planeNormal.clone();
    const point = this.planeOrigin.clone();
    const mouseWvec: Vector3 = ctx2.viewer.sceneSetup.mouseOnPlane(
      mousePoint.x,
      mousePoint.y,
      normal,
      point,
    );

    return mouseWvec;
  }
  change2D = (point: Vector3) => {
    const inTransCenter = new Vector3().fromArray(
      this.faceCSys.inTransformation.apply(point.clone()).toArray(),
    );
    return inTransCenter;
  };
  change3D = (point: Vector3) => {
    const v = new Vector3().fromArray(
      this.faceCSys.outTransformation
        .apply(new Vector3(point.x, point.y, 0))
        .toArray(),
    );
    return v;
  };
  setPlacement = (origin?: Vector3Tuple, type?: PlaneEnum) => {
    if (type === PlaneEnum.FREE) {
      if (!this.pickInfo?.assisObj?.csys) {
        return;
      } else {
        // 默认为 XOY 平面
        this.planeNormal = this.pickInfo.assisObj.csys?.getNormalVc3();
        this.planeOrigin = this.pickInfo.assisObj.csys?.getOriginVc3();
        this.faceCSys = this.pickInfo.assisObj.csys;
      }
      return;
    }
    let Placement = null;
    switch (type) {
      case PlaneEnum.XOY:
        Placement = [origin, [0, 0, 1], [1, 0, 0]];
        break;
      case PlaneEnum.YOZ:
        Placement = [origin, [1, 0, 0], [0, 1, 0]];
        break;
      case PlaneEnum.XOZ:
        Placement = [origin, [0, 1, 0], [1, 0, 0]];
        break;
    }
    const faceCSys: CSys = MathTool.createCsysFromPlacement(Placement);
    this.planeNormal = faceCSys.getNormalVc3();
    this.planeOrigin = faceCSys.getOriginVc3();
    this.faceCSys = faceCSys;
    ctx2.viewer.requestRender();
  };
}
